﻿using System;
using System.Threading;
using System.Threading.Tasks;
using Seagull.Configuration;
using Seagull.Events;
using Seagull.Events.Job;
using Seagull.Model.Alerts;

namespace Seagull.Jobs
{
    public class JobManager
    {
        #region Singleton

        private static readonly Lazy<JobManager> _instance =
            new Lazy<JobManager>(() => new JobManager());

        private RegistrationToken _startJobToken;
        private RegistrationToken _newHourEventToken;

        public static JobManager Instance
        {
            get { return _instance.Value; }
        }

        #endregion

        private JobManager()
        {
        }

        #region Public Methods

        public void Start()
        {
            if(_startJobToken != RegistrationToken.Empty || _newHourEventToken != RegistrationToken.Empty)
            {
                throw new InvalidOperationException("Has already been started.");
            }
            _startJobToken = StartJobEvent.Instance.Register(OnStartJob);
            _newHourEventToken = NewHourEvent.Instance.Register(OnNewHour);
        }

        public void Stop()
        {
            StartJobEvent.Instance.UnRegister(_startJobToken);
            NewHourEvent.Instance.UnRegister(_newHourEventToken);
            _startJobToken = RegistrationToken.Empty;
            _newHourEventToken = RegistrationToken.Empty;
        }

        #endregion

        internal static DateTime AutoJobStartTime
        {
            get { return DateTime.Today.AddDays(-1); }
        }

        private void OnNewHour(DateTime time)
        {
            // 自动任务从昨天开始做
            StartAll(AutoJobStartTime);
        }

        private void OnStartJob(Tuple<JobKind, DateTime> startJobArgs)
        {
            switch(startJobArgs.Item1)
            {
                case JobKind.All:
                    StartAll(startJobArgs.Item2);
                    break;
                case JobKind.Statistic:
                    StartStatTask(startJobArgs.Item2);
                    break;
                default:
                    throw new ArgumentOutOfRangeException("jobKind", startJobArgs.Item1, "Unexpected JobKind.");
            }
        }

        private void StartAll(DateTime startTime)
        {
            var tokenSource = new CancellationTokenSource();
            var cancelToken = tokenSource.Token;
            // 首先开始统计任务
            var compressTask = StartStatTask(startTime)
                // 待其完成后，开始压缩任务
                .ContinueWith(
                    t => StartCompressJob(startTime, tokenSource),
                    cancelToken,
                    TaskContinuationOptions.NotOnCanceled,
                    TaskScheduler.Default);
            ConfigureTaskExceptionHandler(JobKind.Compress, compressTask);

            // 完成后，开始备份任务
            var backupTask = compressTask.ContinueWith(
                t => StartBackupJob(startTime, tokenSource),
                cancelToken,
                TaskContinuationOptions.NotOnCanceled,
                TaskScheduler.Default);

            ConfigureTaskExceptionHandler(JobKind.Backup, backupTask);

            // 完成后，开始清理任务
            var cleanupTask = backupTask.ContinueWith(
                t => StartCleanupJob(startTime, tokenSource),
                cancelToken,
                TaskContinuationOptions.NotOnCanceled,
                TaskScheduler.Default);

            ConfigureTaskExceptionHandler(JobKind.Cleanup, cleanupTask);

            // 最后发布所有任务完成的事件
            cleanupTask.ContinueWith(t =>
            {
                try
                {
                    JobEndedEvent.Instance.Publish(JobKind.All);
                    tokenSource.Dispose();
                }
                catch
                {
                }
            });
        }

        private Task StartStatTask(DateTime startTime)
        {
            var tokenSource = new CancellationTokenSource();

            var statTask = Task.Factory.StartNew(
                () => StartStatisticJob(tokenSource, startTime),
                tokenSource.Token,
                TaskCreationOptions.None,
                TaskScheduler.Default);

            ConfigureTaskExceptionHandler(JobKind.Statistic, statTask);

            statTask.ContinueWith(t => tokenSource.Dispose());

            return statTask;
        }

        #region Actual Job Starters

        private readonly object _jobSyncRoot = new object();

        private JobKind _currentJobKind = JobKind.Unknown;

        private void StartStatisticJob(CancellationTokenSource cancelTokenSource, DateTime startTime)
        {
            StartJob(() =>
            {
                var statJob = new StatisticsJob(startTime);
                statJob.ProgressChanged += (sender, e) =>
                    UpdateProgress(JobKind.Statistic, e.ProgressPercentage, cancelTokenSource);
                statJob.Start(cancelTokenSource.Token);
            }, JobKind.Statistic, cancelTokenSource);
        }

        private void StartCompressJob(DateTime startTime, CancellationTokenSource cancelTokenSource)
        {
            StartJob(() =>
            {
                var dataCompressJob = new CompressJob(
                    Config.Instance.PathConfig.SourceDataFolder,
                    Config.Instance.PathConfig.CompressDataFolder,
                    startTime);
                dataCompressJob.ProgressChanged += (sender, e) =>
                    UpdateProgress(JobKind.Compress,
                        e.ProgressPercentage / (Config.Instance.TaskConfig.IsRawLogBackuped ? 2 : 1), cancelTokenSource);
                dataCompressJob.Start(cancelTokenSource.Token);

                if(Config.Instance.TaskConfig.IsRawLogBackuped)
                {
                    var unparsedLogCompressJob = new CompressJob(
                        Config.Instance.PathConfig.SourceLogFolder,
                        Config.Instance.PathConfig.CompressLogFolder,
                        startTime);
                    unparsedLogCompressJob.ProgressChanged += (sender, e) =>
                        UpdateProgress(JobKind.Compress, 50 + e.ProgressPercentage / 2, cancelTokenSource);
                    unparsedLogCompressJob.Start(cancelTokenSource.Token);
                }
            }, JobKind.Compress, cancelTokenSource);
        }

        private void StartBackupJob(DateTime startTime, CancellationTokenSource cancelTokenSource)
        {
            StartJob(() =>
            {
                var dataBackupJob = new BackupJob(
                    Config.Instance.PathConfig.CompressDataFolder,
                    Config.Instance.PathConfig.DataFolderOnRemovableDevice,
                    startTime);
                dataBackupJob.ProgressChanged += (sender, e) =>
                    UpdateProgress(JobKind.Backup, e.ProgressPercentage / 2, cancelTokenSource);
                dataBackupJob.Start(cancelTokenSource.Token);

                if(Config.Instance.TaskConfig.IsRawLogBackuped)                
                {
                    var unparsedLogBackupJob = new BackupJob(
                        Config.Instance.PathConfig.CompressLogFolder,
                        Config.Instance.PathConfig.LogFolderOnRemovableDevice, startTime);
                    unparsedLogBackupJob.ProgressChanged += (sender, e) =>
                        UpdateProgress(JobKind.Backup, 50 + e.ProgressPercentage/2, cancelTokenSource);
                    unparsedLogBackupJob.Start(cancelTokenSource.Token);
                }
            }, JobKind.Backup, cancelTokenSource);
        }

        private void StartCleanupJob(DateTime startTime, CancellationTokenSource cancelTokenSource)
        {
            StartJob(() =>
            {
                var cleanupJob = new CleanupJob(startTime);
                cleanupJob.Start(cancelTokenSource.Token);
            }, JobKind.Cleanup, cancelTokenSource);
        }

        #endregion

        #region Helpers

        private void StartJob(Action doJob, JobKind jobKind, CancellationTokenSource cancelTokenSource)
        {
            if(Monitor.TryEnter(_jobSyncRoot))
            {
                try
                {
                    _currentJobKind = jobKind;
                    JobStartedEvent.Instance.Publish(Tuple.Create(jobKind, cancelTokenSource));
                    try
                    {
                        doJob();
                    }
                    finally
                    {
                        JobEndedEvent.Instance.Publish(jobKind);
                    }
                }
                finally
                {
                    Monitor.Exit(_jobSyncRoot);
                }
            }
            else
            {
                LogOnlyAlertEvent.Instance.Publish(new GenericAlert
                {
                    AlertContent = string.Format("由于正在{0}，跳过本次{1}",
                        GetJobContextString(_currentJobKind), GetJobContextString(jobKind))
                });
            }
        }

        private static void ConfigureTaskExceptionHandler(JobKind jobKind, Task jobTask)
        {
            // 任务的异常处理
            jobTask.ContinueWith(task =>
            {
                try
                {
                    AlertEvent.Instance.Publish(new ApplicationAlert(task.Exception.InnerException,
                        GetJobContextString(jobKind)));
                }
                catch
                {
                }
            }, TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.ExecuteSynchronously);
        }

        private static void UpdateProgress(JobKind jobKind, int progress, CancellationTokenSource cancelTokenSource)
        {
            JobProgressEvent.Instance.Publish(Tuple.Create(jobKind, progress, cancelTokenSource));
        }

        private static string GetJobContextString(JobKind jobKind)
        {
            switch(jobKind)
            {
                case JobKind.Statistic:
                    return "执行统计任务";
                case JobKind.Compress:
                    return "执行压缩任务";
                case JobKind.Backup:
                    return "执行备份任务";
                case JobKind.Cleanup:
                    return "执行清理任务";
                default:
                    throw new ArgumentOutOfRangeException("jobKind", jobKind, "Unexpected JobKind.");
            }
        }

        #endregion
    }
}
